package org.chen.mybatis.factory;

import org.chen.mybatis.annotation.*;
import org.chen.mybatis.handler.IntegerTypeHandler;
import org.chen.mybatis.handler.StringTypeHandler;
import org.chen.mybatis.handler.TypeHandler;
import org.chen.mybatis.parameter.ParameterMapping;
import org.chen.mybatis.utils.ConnectionUtils;
import org.chen.mybatis.utils.HumpUtils;
import org.chen.spring.ChenApplicationContext;
import org.chen.spring.core.annotation.Autowired;
import org.chen.spring.core.annotation.Component;
import org.chen.spring.core.factory.BeanFactory;

import java.lang.reflect.*;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Mapper代理工厂
 *
 * @author Chen
 * @since 2023/6/9 10:39
 */
public class MapperProxyFactory {

    private  final Map<Class<?>, TypeHandler> typeHandlerMap = new HashMap<>();

    {
        typeHandlerMap.put(String.class, new StringTypeHandler());
        typeHandlerMap.put(Integer.class, new IntegerTypeHandler());
    }

    public <T> T getMapper(Class<T> clazz) {
        Object o = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{clazz}, (proxy, method, args) -> {
            if (method.getReturnType() == void.class) {
                return null;
            }
            String oldSql = null;
            //获取Sql
            if (method.isAnnotationPresent(Select.class)) {
                oldSql = method.getAnnotation(Select.class).value();
            } else if (method.isAnnotationPresent(Insert.class)) {
                oldSql = method.getAnnotation(Insert.class).value();
            } else if (method.isAnnotationPresent(Update.class)) {
                oldSql = method.getAnnotation(Update.class).value();
            } else if (method.isAnnotationPresent(Delete.class)) {
                oldSql = method.getAnnotation(Delete.class).value();
            }
            if (oldSql == null) {
                return method.invoke(proxy, args);
            }
            ParameterMapping parameterMapping = new ParameterMapping(oldSql);
            String sql = parameterMapping.getSql();
            Map<String, Object> parameterMap = new HashMap<>();
            Parameter[] parameters = method.getParameters();
            for (int i = 0, parametersLength = parameters.length; i < parametersLength; i++) {
                Parameter parameter = parameters[i];
                if (parameter.isAnnotationPresent(Param.class)) {
                    parameterMap.put(parameter.getAnnotation(Param.class).value(), args[i]);
                }
                parameterMap.put(parameter.getName(), args[i]);
            }
            Connection connection;
            ConnectionUtils connectionUtils = ChenApplicationContext.getBeanFactory().getBean(ConnectionUtils.class);
            connection = connectionUtils.getConnection();
            //封装SQL
            PreparedStatement preparedStatement = connection.prepareStatement(sql);
            List<String> paramMapping = parameterMapping.getParamMapping();
            for (int i = 0, paramMappingSize = paramMapping.size(); i < paramMappingSize; i++) {
                String paramName = paramMapping.get(i);
                Object param = parameterMap.get(paramName);
                if (param == null) {
                    param = parameterMap.get("arg" + i);
                }
                if (param == null) {
                    preparedStatement.setObject(i + 1, null);
                    continue;
                }
                typeHandlerMap.get(param.getClass()).setParameter(preparedStatement, i + 1, param);
            }
            if (method.isAnnotationPresent(Select.class)) {
                ResultSet resultSet = preparedStatement.executeQuery();
                //获取结果泛型类型
                Class<?> returnType = null;
                Type type = method.getGenericReturnType();
                //处理List类型
                if (type instanceof Class<?>) {
                    //不是泛型
                    returnType = (Class<?>) type;
                } else if (type instanceof ParameterizedType) {
                    //是泛型
                    Type[] actualTypeArguments = ((ParameterizedType) type).getActualTypeArguments();
                    returnType = (Class<?>) actualTypeArguments[0];
                }
                return getResult(type, returnType, resultSet, connection);
            }
            return preparedStatement.executeUpdate();
        });
        return (T) o;
    }

    private Object getResult(Type type, Class<?> returnType, ResultSet resultSet, Connection connection) throws
            Exception {
        //结果的元数据
        ResultSetMetaData metaData = resultSet.getMetaData();
        //初始化列名列表
        List<String> coulmnList = new ArrayList<>(metaData.getColumnCount());
        for (int i = 0; i < metaData.getColumnCount(); i++) {
            //获取列名并存入列表中
            coulmnList.add(metaData.getColumnName(i + 1));
        }

        List<Object> result = new ArrayList<>();

        //封装结果
        while (resultSet.next()) {
            //一行数据
            Object instance = returnType.getDeclaredConstructor().newInstance();
            for (String name : coulmnList) {
                //将列名转为驼峰命名法
                String humpName = HumpUtils.underlineToCamel(name);
                //根据字段名获取字段
                Field field = returnType.getDeclaredField(humpName);
                //设置可访问
                field.setAccessible(true);
                //设置该字段的值为查询结果中对应的值
                //此处需要根据字段类型获取对应的类型处理器
                field.set(instance, typeHandlerMap.get(field.getType()).getResult(resultSet, name));
            }
            result.add(instance);
        }
        connection.close();
        if (type.equals(List.class)) {
            return result;
        } else {
            if (result.size() > 1) {
                throw new RuntimeException("查询一条数据,但是查询到了多条数据");
            }
            return result.isEmpty() ? null : result.get(0);
        }
    }

    public static void init(Class<?> clazz) {
        BeanFactory.registerBean(MapperProxyFactory.class.getSimpleName(), new MapperProxyFactory());
        String value = clazz.getPackageName();
        boolean annotationPresent = clazz.isAnnotationPresent(MapperScan.class);
        if (annotationPresent) {
            value = clazz.getAnnotation(MapperScan.class).value();
        }
        BeanFactory.resisterScanBean(new MapperScanBean(List.of(value, "org.chen.mybatis"), annotationPresent));
    }


}
